package de.mrapp.util.datastructure;

import java.util.*;

public class ListenerList<T> implements Iterable<T> {

    private Object lock = new Object();

    private List<T> listeners = new ArrayList<>();

    private CompareMethod compareMethod;

    public ListenerList() {
        this.compareMethod = CompareMethod.EQUALITY;
    }

    @Override
    public Iterator<T> iterator() {
        synchronized (lock) {
            return listeners.iterator();
        }
    }

    private boolean contains(Iterable<T> iterable, T listener) {
        T tempIt = null;
        for (T it : iterable) {
            if (equals(it, listener)) {
                tempIt = it;
                break;
            }
        }
        return tempIt != null;
    }

    private boolean equals(T listener1, T listener2) {
        if (listener1 == null) {
            return listener2 == null;
        }
        if (listener2 != null) {
            switch (compareMethod) {
                case EQUALITY:
                    return listener1.equals(listener2);
                case IDENTITY:
                    return listener1 == listener2;
            }
        }
        return false;
    }

    public boolean isEmpty() {
        synchronized (lock) {
            return listeners.isEmpty();
        }
    }

    public int size() {
        synchronized (lock) {
            return listeners.size();
        }
    }

    public boolean add(T listener) {
        synchronized (lock) {
            if (!contains(listeners, listener)) {
                List<T> newList = new LinkedList<>(listeners);
                newList.add(listener);
                listeners = newList;
                return true;
            }

        }
        return false;
    }

    public void addAll(Iterable<T> iterable) {
        synchronized (lock) {
            List<T> newList = null;
            for (T listener : iterable) {
                if (listener == null) {
                    throw new IllegalArgumentException("The listener may not be null");
                }
                boolean contains = false;

                if (newList == null) {
                    contains = contains(listeners, listener);
                } else {
                    contains = contains(newList, listener);
                }

                if (!contains) {
                    if (newList == null) {
                        newList = new LinkedList<>(listeners);
                    }
                    newList.add(listener);
                }
            }

            if (newList != null) {
                listeners = newList;
            }
        }
    }

    public boolean remove(T listener) {
        synchronized (lock) {
            if (contains(listeners, listener)) {
                List<T> newList = new LinkedList<>();
                for (T it : listeners) {
                    if (!equals(it, listener)) {
                        newList.add(it);
                    }
                }
                listeners = newList;
                return true;
            }
        }
        return false;
    }

    public void removeAll(Iterable<T> iterable) {
        synchronized (lock) {
            List<T> newList = null;
            for (T listener : listeners) {
                if (!contains(iterable, listener)) {
                    if (newList == null) {
                        newList = new LinkedList<>();
                    }
                    newList.add(listener);
                }
            }

            if (newList != null) {
                listeners = newList;
            }
        }
    }

    public void clear() {
        synchronized (lock) {
            this.listeners = Collections.emptyList();
        }
    }

    public Collection<T> getAll() {
        synchronized(lock) {
            if(isEmpty()){
                return Collections.emptyList();
            }else {
                LinkedList<T> linkedList = new LinkedList<>(this.listeners);
                return Collections.unmodifiableCollection(linkedList);
            }
        }
    }


    enum CompareMethod {
        /**
         * If listeners should be compared using the [Object.equals] method.
         */
        EQUALITY,

        /**
         * If listeners should be compared using the identity (==) operator.
         */
        IDENTITY
    }
}
